// 实现对数组响应式的拦截
const methods = ["shift", "unshift", "push", "pop", "splice", "reverse", "sort"]

const arrProtoType = Object.create(Array.prototype)

methods.forEach(method => {
  // 覆盖原有数组的方法
  arrProtoType[method] = function () {
    Array.prototype[method].call(this, ...arguments)
    // TODO 新增操作： 通知更新

  }
})

// 实现对象响应式拦截
const defineReactive = (obj, key, val) => {
  new Observer(val)
  // 创建dep实例和可以对应
  const dep = new Dep()
  return Object.defineProperty(obj, key, {
    get: () => {
      // 在这里做依赖收集
      Dep.target && dep.addDep(Dep.target)
      return val
    },
    set: (v) => {
      if (v !== val) {
        new Observer(v)
        val = v
        dep.notify()
      }
    }
  })
}
// proxy代理函数：让⽤户可以直接访问data中的key
function proxy(vm, key) {
  Object.keys(vm[key]).forEach((k) => {
    Object.defineProperty(vm, k, {
      get() {
        return vm[key][k];
      },
      set(v) {
        vm[key][k] = v;
      },
    });
  });
}
// 根据传⼊value类型做不同操作
class Observer {
  constructor(value) {
    this.value = value;
    // 判断⼀下value类型
    // 遍历对象
    this.walk(value);
  }
  walk(obj) {
    if (typeof obj !== "object" || obj === null) {
      return obj
    }
    if (Array.isArray(obj)) {
      // 覆盖原型，替换我们自己的
      obj.__proto__ = arrProtoType;
      Object.keys(obj).forEach(key => new Observer(obj[key]))
    }
    else {
      Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]))
    }
  }
}

class Vue {
  constructor(options) {
    // 0.保存options
    this.$options = options;
    this.$data = options.data;
    // 1.将data做响应式处理
    new Observer(this.$data);
    // 2.为$data做代理
    proxy(this, "$data");
    // 3.编译模板
    if (options.el) {
      this.$mount(options.el);
    }
  }

  // 添加$mount
  $mount(el) {
    this.$el = document.querySelector(el);
    // 1.声明updateComponent
    const updateComponent = () => {
      // 渲染获取视图结构
      // const el = this.$options.render.call(this);
      // // 结果追加
      // const parent = this.$el.parentElement;
      // parent.insertBefore(el, this.$el.nextSibling);
      // parent.removeChild(this.$el);
      // this.$el = el;

      // vnode方案
      const vnode = this.$options.render.call(this, this.$createElement);
      this._update(vnode);
    };
    // 2.new Watcher
    new Watcher(this, updateComponent);
  }

  // 接收虚拟dom，转换为dom
  _update(vnode) {
    const prevVnode = this._vnode;

    if (!prevVnode) {
      // init
      this.__patch__(this.$el, vnode);
    } else {
      // update：diff
      this.__patch__(prevVnode, vnode);
    }

    this._vnode = vnode;
  }

  __patch__(oldVnode, vnode) {
    if (oldVnode.nodeType) {
      // init
      const parent = oldVnode.parentElement;
      const refElm = oldVnode.nextSibling;
      // 递归创建dom结构
      const el = this.createElm(vnode);
      parent.insertBefore(el, refElm);
      parent.removeChild(oldVnode);
    } else {
      // update
      // 操作谁
      const el = (vnode.el = oldVnode.el);
      // diff
      if (oldVnode.tag === vnode.tag) {
        // sameVnode
        // props...
        // children
        const oldCh = oldVnode.children;
        const newCh = vnode.children;

        if (typeof newCh === "string") {
          if (typeof oldCh === "string") {
            // text update
            if (newCh !== oldCh) {
              el.textContent = newCh;
            }
          } else {
            // replace elements with text
            el.textContent = newCh;
          }
        } else {
          if (typeof oldCh !== "string") {
            // updateChildren
            this.updateChildren(el, oldCh, newCh);
          } else {
            // replace text with elements
            el.textContent = "";
            newCh.forEach((ch) => {
              el.appendChild(this.createElm(ch));
            });
          }
        }
      } else {
        // replace
      }
    }
  }

  // 不优化，不考虑key
  updateChildren(parentElm, oldCh, newCh) {
    // 这⾥暂且直接patch对应索引的两个节点
    const len = Math.min(oldCh.length, newCh.length);
    for (let i = 0; i < len; i++) {
      this.__patch__(oldCh[i], newCh[i]);
    }
    // newCh若是更⻓的那个，说明有新增
    if (newCh.length > oldCh.length) {
      newCh.slice(len).forEach((child) => {
        const el = this.createElm(child);
        parentElm.appendChild(el);
      });
    } else if (newCh.length < oldCh.length) {
      // oldCh若是更⻓的那个，说明有删减
      oldCh.slice(len).forEach((child) => {
        parentElm.removeChild(child.el);
      });
    }
  }

  // 递归创建dom树
  createElm(vnode) {
    const el = document.createElement(vnode.tag);

    // todo: props
    // children
    if (vnode.children) {
      if (typeof vnode.children === "string") {
        // text
        el.textContent = vnode.children;
      } else {
        // array children
        vnode.children.forEach((n) => {
          const child = this.createElm(n);
          el.appendChild(child);
        });
      }
    }

    // 保存真实dom用于更新
    vnode.el = el;

    return el;
  }

  // 返回vnode
  $createElement(tag, props, children) {
    return { tag, props, children };
  }
}

class Watcher {
  constructor(vm, fn) {
    this.vm = vm;
    this.getter = fn;

    this.get();
  }

  get() {
    // 依赖收集触发
    Dep.target = this;
    this.getter.call(this.vm);
    Dep.target = null;
  }

  update() {
    this.get();
  }
}
// 管家：和某个key，⼀⼀对应，管理多个秘书，数据更新时通知他们做更新⼯作
class Dep {
  constructor() {
    this.deps = new Set();
  }
  addDep(watcher) {
    this.deps.add(watcher);
  }
  notify() {
    this.deps.forEach((watcher) => watcher.update());
  }
}
